<?php
/**
 * Groo ISBN Class.
 *
 * $Horde: incubator/groo/lib/ISBN.php,v 1.6 2006/08/16 03:17:16 luciano Exp $
 *
 * Copyright 2004-2006 Luciano Ramalho <luciano@ramalho.org>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * @author  Luciano Ramalho <luciano@ramalho.org>
 * @package Groo
 */


//error_reporting(E_ALL | E_STRICT);
//assert_options(ASSERT_ACTIVE,1);
//assert_options(ASSERT_WARNING,1); assert_options(ASSERT_BAIL,1); 

class ISBN {
    
    static function filterDigits($input) {
        /* Strip the input of all non-digits (retaining last X if present) */
        $input = trim($input);
        $digits = array();
        $last_index = strlen($input)-1;
        for ($i=0; $i<=$last_index; $i++) {
            $char = $input{$i};
            if (ctype_digit($char)) {
                $digits[] = $char;
            } elseif ($i == $last_index and strtoupper($char)=='X') {
                // an X may appear as check digit in ISBN-10
                $digits[] = 'X';
            }
        }
        return join($digits,'');
    }

    static function checksumISBN10($digits) {
        /*  Return ISBN-10 check digit as a string (may be 0-9 or X)
            References:
            http://www.isbn-international.org/en/userman/chapter4.html#usm4_4
            http://www.bisg.org/isbn-13/conversions.html
        */
        $sum = 0;
        for ($i=0,$weight=10; $weight>=2; $i++,$weight--) {
            $sum += (int)$digits{$i} * $weight;
        }
        $remainder = $sum % 11;
        $check = 11 - $remainder;
        if     ($check == 10) { return 'X'; }
        elseif ($check == 11) { return '0'; }
        else                  { return (string)$check; }
    }

    static function checksumEAN($digits) {
        /*  Return EAN check digit as a string (may be 0-9)
            Every ISBN-13 is a valid EAN
            Reference:
            http://www.bisg.org/isbn-13/conversions.html
        */
        $sum = 0;
        for ($i=0,$weight=1; $i<12; $i++,$weight=$i%2*2+1) {
            $sum += (int)$digits{$i} * $weight;
        }
        $remainder = $sum % 10;
        $check = 10 - $remainder;
        if     ($check == 10) { return '0'; }
        else                  { return (string)$check; }
    }
    
    static function isValidEAN($digits) {
        if (strlen($digits) != 13) {
            $digits = ISBN::filterDigits($digits);
            if (strlen($digits) != 13) { return false; }
        }
        return $digits{12} == ISBN::checksumEAN($digits);
    }
    
    static function isValidISBN13($digits) {
        if (strlen($digits) != 13) {
            $digits = ISBN::filterDigits($digits);
            if (strlen($digits) != 13) { return false; }
        }
        $prefix = substr($digits,0,3);
        if ($prefix!='978' and $prefix!='979') { return false; }
        return ISBN::isValidEAN($digits);
    }

    static function isValidISBN10($digits) {
        if (strlen($digits) != 10) {
            $digits = ISBN::filterDigits($digits);
            if (strlen($digits) != 10) { return false; }
        }
        return strtoupper($digits{9}) == ISBN::checksumISBN10($digits);
    }

    static function isValidISBN($digits) {
        return ISBN::isValidISBN10($digits) or ISBN::isValidISBN13($digits);   
    }


    static function hasValidISBN10($digits) {
        if (!ISBN::isValidISBN13($digits)) { return false; }
        $prefix = substr($digits,0,3);
        return $prefix=='978';
    }

    static function convertISBN13toISBN10($digits) {
        assert ('ISBN::isValidISBN13($digits)');
        $digits = substr($digits,3,9);
        return $digits . ISBN::checksumISBN10($digits);
    }

    static function convertISBN10toISBN13($digits) {
        // EAN format and encoding is the same as ISBN-13
        assert ('ISBN::isValidISBN10($digits)');
        $digits = '978'.substr($digits,0,9);
        return $digits . ISBN::checksumEAN($digits);
    }

    static function convertEANtoISBN($digits) {
        if ('ISBN::hasValidISBN10($digits)') {
            return ISBN::convertISBN13toISBN10($digits);
        } elseif ('ISBN::isValidISBN13($digits)') {
            return $digits;
        } else {
            throw new Exception("cannot convert '$digits' to ISBN");    
        }
    }

}
/*** smoke tests ***/
if (isset($argc)) {
    if ($argc==3 and $argv[1]=='--check-isbn') { // analyze a number given as argv[1]
        $digits = ISBN::filterDigits($argv[2]);
        echo "digits (".strlen($digits)."):\t$digits\n";
        if (strlen($digits) == 10) {
            echo "isValidISBN:\t".(string)ISBN::isValidISBN($digits)."\n";       
            echo "isValidISBN10:\t".(string)ISBN::isValidISBN10($digits)."\n";       
            echo "convertISBN10toISBN13:\t".(string)ISBN::convertISBN10toISBN13($digits)."\n";       
        } elseif (strlen($digits) == 13) {
            echo "isValidISBN:\t".(string)ISBN::isValidISBN($digits)."\n";       
            echo "isValidISBN13:\t".(string)ISBN::isValidISBN13($digits)."\n";
            echo "isValidEAN:\t".(string)ISBN::isValidEAN($digits)."\n";
            echo "hasValidISBN10:\t".(string)ISBN::hasValidISBN10($digits)."\n";       
            echo "convertISBN13toISBN10:\t".(string)ISBN::convertISBN13toISBN10($digits)."\n";       
        } else {
            echo "notice:\tAn ISBN must have 10 or 13 digits.\n";    
        } 
    }
    elseif ($argc==3 and $argv[1]=='--gen-fake-isbn13') {
        $digits = $argv[2];
        if (strlen($digits) < 9) {
            while (strlen($digits)<9) { $digits = $digits.'0'; }
        } elseif (strlen($digits) > 9) {
            throw new Exception('arg[2] should have 1 to 9 digits');    
        }
        $digits = '979'.$digits;
        echo $digits.ISBN::checksumEAN($digits)."\n";
    }
}
?>